﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;

namespace StripeOne.Core.Ftp
{
    public class StripeOneFtpClient
    {
        #region Attributes
        
        private string _server;
        private int _port;
        private string _username;
        private string _password;
        private bool _enableSSL;
        private int _blocksize = 262144;

        #endregion

        #region Public Methods

        public StripeOneFtpClient(string server, int port, string username, string password, bool enableSSL = false)
        {
            this._server = server;
            this._port = port;
            this._username = username;
            this._password = password;
            this._enableSSL = enableSSL;
        }
        
        public StripeOneFtpClientFile[] GetList(string path)
        {
            FtpWebRequest request = this.GetRequest(path, string.Empty);
            request.Method = "LIST";
            request.KeepAlive = false;
            string input;
            using (FtpWebResponse ftpWebResponse = (FtpWebResponse)request.GetResponse())
            {
                using (Stream responseStream = ftpWebResponse.GetResponseStream())
                {
                    using (StreamReader streamReader = new StreamReader(responseStream))
                    {
                        input = streamReader.ReadToEnd();
                    }
                }
            }
            Regex regex = new Regex("^([d-])([rwxt-]{3}){3}\\s+\\d{1,}\\s+.*?(\\d{1,})\\s+(\\w+\\s+\\d{1,2}\\s+(?:\\d{4})?)(\\d{1,2}:\\d{2})?\\s+(.+?)\\s?$", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
            List<StripeOneFtpClientFile> list = new List<StripeOneFtpClientFile>();
            foreach (Match match in regex.Matches(input))
            {
                list.Add(new StripeOneFtpClientFile
                {
                    Name = match.Groups[6].Value,
                    IsDirectory = match.Groups[1].Value == "d",
                    Size = Convert.ToInt64(match.Groups[3].Value)
                });
            }
            return list.ToArray();
        }
        
        public void Download(string path, string file, string destFile)
        {
            this.Download(path, file, () => File.OpenWrite(destFile));
        }
        
        public void Download(string path, string file, Func<Stream> destOpener)
        {
            this.CopyStream(() => this.GetDownloadStream(path, file), destOpener);
        }
        
        public Stream GetDownloadStream(string path, string file)
        {
            long size = this.GetList(path).First((StripeOneFtpClientFile f) => f.Name == file).Size;
            FtpWebRequest request = this.GetRequest(path, file);
            request.UseBinary = true;
            request.KeepAlive = false;
            request.Method = "RETR";
            FtpWebResponse resp = (FtpWebResponse)request.GetResponse();
            return new StripeOneFtpClientStream(resp, size);
        }
        
        public void Upload(string path, string file, string sourceFile)
        {
            this.Upload(path, file, () => File.OpenRead(sourceFile));
        }
        
        public void Upload(string path, string file, Func<Stream> sourceOpener)
        {
            this.CopyStream(sourceOpener, () => this.GetUploadStream(path, file));
        }
        
        public Stream GetUploadStream(string path, string file)
        {
            FtpWebRequest request = this.GetRequest(path, file);
            request.UseBinary = true;
            request.KeepAlive = false;
            request.Method = "STOR";
            return new StripeOneFtpClientStream(request, 0L);
        }
        
        public void Delete(string path, string file)
        {
            FtpWebRequest request = this.GetRequest(path, file);
            request.UseBinary = true;
            request.KeepAlive = false;
            request.Method = "DELE";
            request.GetResponse();
        }
        
        public void MakeDirectory(string path)
        {
            FtpWebRequest request = this.GetRequest(path, "");
            request.UseBinary = true;
            request.KeepAlive = false;
            request.Method = "MKD";
            request.GetResponse();
        }
        
        public void RemoveDirectory(string path)
        {
            FtpWebRequest request = this.GetRequest(path, "");
            request.UseBinary = true;
            request.KeepAlive = false;
            request.Method = "RMD";
            request.GetResponse();
        }

        #endregion

        #region Private Methods

        private FtpWebRequest GetRequest(string path, string file)
        {
            if (!path.StartsWith("/"))
            {
                path = "/" + path;
            }
            if (!file.StartsWith("/"))
            {
                file = "/" + file;
            }
            if (path.Length > 1 && path.EndsWith("/"))
            {
                path = path.Substring(0, path.Length - 1);
            }
            FtpWebRequest ftpWebRequest = (FtpWebRequest)WebRequest.Create(string.Concat(new object[]
			{
				"ftp://",
				this._server,
				":",
				this._port,
				path,
				file
			}));
            ftpWebRequest.EnableSsl = this._enableSSL;
            ftpWebRequest.Credentials = new NetworkCredential(this._username, this._password);
            return ftpWebRequest;
        }
        
        private void CopyStream(Func<Stream> sourceOpener, Func<Stream> destOpener)
        {
            using (Stream stream = sourceOpener())
            {
                using (Stream stream2 = destOpener())
                {
                    byte[] array = new byte[this._blocksize];
                    int count;
                    while ((count = stream.Read(array, 0, array.Length)) > 0)
                    {
                        stream2.Write(array, 0, count);
                    }
                }
            }
        }

        #endregion
    }
}
